Add mapi command - #69
Merged
Merged
Conversation
IvanKiral
force-pushed
the
add_mapi_command
branch
from
August 19, 2026 08:47
e189222 to
4b88237
Compare
IvanKiral
marked this pull request as ready for review
September 2, 2026 10:31
JiriLojda
requested changes
Sep 2, 2026
JiriLojda
requested changes
Sep 3, 2026
IvanKiral
force-pushed
the
add_mapi_command
branch
from
September 4, 2026 10:50
0ccb043 to
ce4c358
Compare
JiriLojda
approved these changes
Sep 4, 2026
stdout is reserved for command payloads; progress, warnings and errors go to stderr. Handlers build a Logger via createLoggerFromArgs and pass it into core. Warnings get a yellow prefix.
* feat: add `kontent mapi` raw Management API passthrough command * refactor: polish raw mapi command and harden its tests
…t.ai environment
* feat: generate command reference docs from yargs definitions into colocated READMEs * ci: fail when generated command docs are stale
E2E_ENV_ID_FILE moves to step-level env (runner context is invalid in job env). Unset E2E_* now errors instead of silently skipping the suite.
- drop yargs .env("KONTENT"); read env vars explicitly, implement KONTENT_MAPI_KEY
- --header nargs(1), reject GET with --input, guard backslash traversal
- payloads always on stdout, non-JSON body reported by content type, swallow EPIPE
- stream --input via openAsBlob
- retry, Retry-After and header merging handed to core-sdk
yargs resolves `--version` by walking up from its own location to the nearest package.json, which in a bundled install is not ours. Pass the same `cliVersion` telemetry already reads, so the flag and the reported version cannot drift. Also ignore `*.tgz`, the output of `pnpm pack`.
…nput Error messages walk the undici cause chain. --envId gets the traversal guard. --input accepts a pipe or /dev/stdin; signals are re-raised after the telemetry flush so a blocked open can die.
IvanKiral
force-pushed
the
add_mapi_command
branch
from
September 7, 2026 06:11
ce4c358 to
1671bd5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
kontent mapi <endpoint>, a curl-like passthrough to the Kontent.ai Management API: you give it a path, it authenticates the request and prints the response. Also lands the two things that command needed to exist cleanly — a proper stdout/stderr split across the whole CLI, and generated command reference docs — plus an e2e suite that runs the built binary against a real cloned environment.What's in it
kontent mapi(src/commands/mapi,src/core/mapi,src/lib/mapi/raw)kontent mapi 'types?limit=10' --envId <id>,-X/--method, repeatable-H/--header,--input <file|->,-i/--include.Authorizationheader →--mapiKey→KONTENT_MAPI_KEY→ the stored login token. Reading the env var directly (not as a yargs option) keeps the key off argv, out ofpsand shell history.Logging refactor (
src/log.ts) — all logging goes to stderr through an injectableLogger;--logLevel none|standard|verboseand--verbose.Generated docs (
scripts/generateCommandDocs.ts) —pnpm docs:generatereplays the yargs registrations against a recording proxy and rewrites the root README table plus each command folder's<!-- reference -->block. CI fails when they're stale.e2e (
test/e2e) — clone-per-run from an empty template environment, gated onE2E_MAPI_KEY/E2E_SOURCE_ENV_ID, own vitest config, separate workflow (fork PRs skipped, no secret access).Decisions worth calling out
Output channels. stdout carries only the data the command exists to produce and is never level-gated; stderr carries everything said about producing it.
--logLevel nonemust still print a response body — a payload is not a log. This also fixedkontent telemetry status, which logged its report atinfoand so vanished under--logLevel noneand never piped togrep; core now returns the report and the command writes it out.Which HTTP layer to sit on. Went through the full loop here. Not
getDefaultHttpService— it maps every non-2xx to an error and keeps the body only when it matches the Kontent error shape, which would lose exactly the 4xx bodies this command exists to show. Briefly replaced core-sdk'sHttpAdapterwith a bespoke raw-bytes transport so a CSV or binary body wouldn't come back as empty stdout — then reverted it: MAPI answersapplication/jsonon every endpoint and every status, and binary only ever travels request-side on an asset upload. A second HTTP path with its own abort and header handling was cost for a case that does not arise. The adapter stays; the body decision is made from the response's content type, not from the payload (core-sdk yieldsnullfor absent, skipped, and literal-JSON-nullbodies alike), and a non-JSON body is reported on stderr rather than silently vanishing.Endpoint scoping is a guard, not a privilege boundary. The host is always pinned to MAPI and the caller only escapes their own scoping — but the guard split on forward slashes only, so
'types\..\..\secret'walked past it (WHATWG treats backslashes as separators in an https URL). Now splits on both. Percent-encoded separators need no handling:%2f/%5cstay encoded and can't traverse; only%2e%2edecodes into a traversing segment, which the per-segment decode already covered.429 backoff. Bounded, abortable, and RFC-correct. Past a minute the API is rationing quota rather than smoothing a burst, so the 429 goes back to the caller with a warning naming the requested delay instead of sleeping an hour three times over. The sleep runs through
node:timers/promiseswith the request's abort signal — the command installs a SIGINT handler, so the first Ctrl+C previously did nothing at all.Number()accepted values delta-seconds does not (""→ 0,-5/1.5fell through toDate.parseas years → a past date → immediate retry); parsing now requires1*DIGIT, and the HTTP-date branch requires a letter.Dropped
.env("KONTENT"). It turned everyKONTENT_*var in the shell into a CLI flag, and.strict()then rejected the ones the running command didn't declare — an unrelatedKONTENT_PROJECT_IDbroke every command, and a strayKONTENT_INPUTsilently turned a plain listing into a POST of that file. Nothing depended on the mapping; every supported variable is read fromprocess.envwhere it applies.-X GET --inputis rejected up front. curl allows a GET with a body, but undici refuses one, so this used to die on a rawRequest with GET/HEAD method cannot have body.This is where curl parity stops.--inputsendsapplication/jsonunless a header overrides it — documented explicitly, because MAPI stores that header as the asset's MIME type, so a PNG uploaded without-Hwas served as JSON with exit code 0 and no warning.Checklist
How to test
test/unitcovers endpoint resolution, header/method parsing, credential resolution,Retry-After, response presentation, and a test that drives the real core-sdk adapter to assert the duplicated JSON content-type rule still agrees with it.test/integrationcovers command-level behavior by foldingregisterover a real yargs instance. Several fixes were also verified against the built binary (noted in their commits).Manually, against a real environment:
End-to-end:
pnpm test:e2ewithE2E_MAPI_KEYandE2E_SOURCE_ENV_IDset (clones an environment per run).